package com.googlecode.framework.web.pagination;

import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.UnhandledException;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

public class DefaultHref implements Href {
    
    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = -3876310351863523000L;

    public static final String AMPERSAND = "&amp;";
    
    /**
     * Base url for the href.
     */
    private String url;

    /**
     * Url parameters.
     */
    private Map parameters;

    /**
     * Anchor (to be added at the end of URL).
     */
    private String anchor;

    /**
     * Construct a new Href parsing a URL. Parameters are stripped from the base
     * url and saved in the parameters map.
     * 
     * @param baseUrl
     */
    public DefaultHref(String baseUrl) {
        this.parameters = new HashMap();
        setFullUrl(baseUrl); 
    }

    /*
     * (non-Javadoc)
     * @see com.googlecode.framework.web.pagination.Href#setFullUrl(java.lang.String)
     */
    public void setFullUrl(String baseUrl) {
        this.url = null;
        this.anchor = null;
        String noAnchorUrl;
        int anchorposition = baseUrl.indexOf('#');

        // 提取#后面的参数
        if (anchorposition != -1) {
            noAnchorUrl = baseUrl.substring(0, anchorposition);
            this.anchor = baseUrl.substring(anchorposition + 1);
        } else {
            noAnchorUrl = baseUrl;
        }

        if (noAnchorUrl.indexOf('?') == -1) {
            // url中没有参数
            this.url = noAnchorUrl;
            return;
        }

        // 提取Url中的参数，将参数设置到parameters Map中
        StringTokenizer tokenizer = new StringTokenizer(noAnchorUrl, "?");
        if (baseUrl.startsWith("?")) {
            // support fake URI's which are just parameters to use with the current uri
            url = "";
        } else {
            // base url (before "?")
            url = tokenizer.nextToken();
        }

        if (!tokenizer.hasMoreTokens()) {
            return;
        }

        // process parameters
        StringTokenizer paramTokenizer = new StringTokenizer(tokenizer.nextToken(), "&");
        // 分割参数 (key=value)
        while (paramTokenizer.hasMoreTokens()) {
            // split key and value ...
            String[] keyValue = StringUtils.split(paramTokenizer.nextToken(), '=');

            String escapedKey = keyValue[0];
            String escapedValue = keyValue.length > 1 ? keyValue[1] : "";

            if (!this.parameters.containsKey(escapedKey)) {
                // ... and add it to the map
                this.parameters.put(escapedKey, escapedValue);
            } else {
                // additional value for an existing parameter
                Object previousValue = this.parameters.get(escapedKey);
                if (previousValue != null && previousValue.getClass().isArray()) {
                    Object[] previousArray = (Object[]) previousValue;
                    Object[] newArray = new Object[previousArray.length + 1];

                    int j;
                    for (j = 0; j < previousArray.length; j++) {
                        newArray[j] = previousArray[j];
                    }
                    newArray[j] = escapedValue;
                    this.parameters.put(escapedKey, newArray);
                } else {
                    this.parameters.put(escapedKey, new Object[] { previousValue, escapedValue });
                }
            }
        }
    }

    /**
     * 添加一个Parameter
     * 
     * @param name
     * @param value 
     * @return 
     */
    public Href addParameter(String name, Object value) {
        if (value == null) {
            return this;
        }
        
        Object previousValue = this.parameters.get(name);
        if (previousValue == null) {
            this.parameters.put(name, value);
            return this;
        }
        
        if (previousValue != null && previousValue.getClass().isArray()) {
            Object[] previousArray = (Object[]) previousValue;
            Object[] newArray = new Object[previousArray.length + 1];

            int j;
            for (j = 0; j < previousArray.length; j++) {
                newArray[j] = previousArray[j];
            }
            newArray[j] = value;
            this.parameters.put(name, newArray);
        } else {
            this.parameters.put(name, new Object[] { previousValue, value });
        }
        return this;
    }

    /**
     * 删除一个Parameter
     * 
     * @param name
     */
    public Object removeParameter(String name) {
        return this.parameters.remove(name);
    }

    /**
     * 返回所有参数
     */
    public Map getParameterMap() {
        Map copyMap = new HashMap(this.parameters.size());
        copyMap.putAll(this.parameters);
        return copyMap;
    }

    /**
     * 添加所有参数
     */
    public void setParameterMap(Map parametersMap) {
        // create a new HashMap
        this.parameters = new HashMap(parametersMap.size());

        // copy the parameters
        addParameterMap(parametersMap);
    }

    /**
     * Adds all the parameters contained in the map to the Href. The value in
     * the given Map will be escaped before added. Parameters in the original
     * href are kept and not overridden.
     * 
     * @param parametersMap
     *            Map containing parameters
     */
    public void addParameterMap(Map parametersMap) {
        // handle nulls
        if (parametersMap == null) {
            return;
        }
        Iterator mapIterator = parametersMap.entrySet().iterator();
        while (mapIterator.hasNext()) {
            Map.Entry entry = (Map.Entry) mapIterator.next();
            String key = (String) entry.getKey();
            // don't overwrite parameters
            if (!this.parameters.containsKey(key)) {
                Object value = entry.getValue();
                this.parameters.put(key, value);
            }
        }
    }

    /**
     * Getter for the base url (without parameters).
     * 
     * @return String
     */
    public String getBaseUrl() {
        return this.url;
    }

    /**
     * Returns the URI anchor.
     * 
     * @return anchor or <code>null</code> if no anchor has been set.
     */
    public String getAnchor() {
        return this.anchor;
    }

    /**
     * Setter for the URI anchor.
     * 
     * @param name
     *            string to be used as anchor name (without #).
     */
    public void setAnchor(String name) {
        this.anchor = name;
    }

    /**
     * toString: output the full url with parameters.
     * 
     * @return String
     */
    public String toString() {
        StringBuffer buffer = new StringBuffer(30);

        buffer.append(this.url);

        if (this.parameters.size() > 0) {
            buffer.append('?');
            Set parameterSet = this.parameters.entrySet();

            Iterator iterator = parameterSet.iterator();

            while (iterator.hasNext()) {
                Map.Entry entry = (Map.Entry) iterator.next();

                Object key = entry.getKey();
                Object value = entry.getValue();

                if (value == null) {
                    buffer.append(key).append('='); // no value
                } else if (value.getClass().isArray()) {
                    Object[] values = (Object[]) value;
                    for (int i = 0; i < values.length; i++) {
                        if (i > 0) {
                            buffer.append(AMPERSAND);
                        }

                        // TODO URLEncoder.encode(value, "UTF-8");
                        buffer.append(key).append('=').append(values[i]);
                    }
                } else {
                    // TODO URLEncoder.encode(value, "UTF-8");
                    buffer.append(key).append('=').append(value);
                }

                if (iterator.hasNext()) {
                    buffer.append(AMPERSAND);
                }
            }
        }

        if (this.anchor != null) {
            buffer.append('#');
            buffer.append(this.anchor);
        }

        return buffer.toString();
    }

    /**
     * @see java.lang.Object#clone()
     */
    public Object clone() {
        final DefaultHref href;
        try {
            href = (DefaultHref) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new UnhandledException(e);
        }

        href.parameters = new HashMap(this.parameters);
        return href;
    }

    /**
     * @see java.lang.Object#equals(Object)
     */
    public boolean equals(Object object) {
        if (!(object instanceof DefaultHref)) {
            return false;
        }
        DefaultHref rhs = (DefaultHref) object;
        return new EqualsBuilder().append(this.parameters, rhs.parameters).append(this.url, rhs.url)
                .append(this.anchor, rhs.anchor).isEquals();
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
        return new HashCodeBuilder(1313733113, -431360889).append(this.parameters).append(this.url).append(this.anchor)
                .toHashCode();
    }

}
